﻿using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;

namespace Xmart.Expressions.DynamicPredicate;

/// <summary>
/// See http://www.albahari.com/expressions for information and examples.
/// </summary>
public static class LambdaHelpers
{
    private class RebindParameterVisitor : ExpressionVisitor
    {
        private readonly ParameterExpression _oldParameter;
        private readonly ParameterExpression _newParameter;

        public RebindParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
        {
            _oldParameter = oldParameter;
            _newParameter = newParameter;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == _oldParameter ? _newParameter : base.VisitParameter(node);
        }
    }

    /// <summary> OR </summary>
    public static Expression<Func<T, bool>> Or<T>([NotNull] Expression<Func<T, bool>> expr1,
        [NotNull] Expression<Func<T, bool>> expr2)
    {
        var expr2Body = new RebindParameterVisitor(expr2.Parameters[0], expr1.Parameters[0]).Visit(expr2.Body);
        return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, expr2Body), expr1.Parameters);
    }

    /// <summary> OR </summary>
    public static LambdaExpression Or([NotNull] LambdaExpression expr1,
        [NotNull] LambdaExpression expr2)
    {
        var expr2Body = new RebindParameterVisitor(expr2.Parameters[0], expr1.Parameters[0]).Visit(expr2.Body);
        return Expression.Lambda(Expression.OrElse(expr1.Body, expr2Body), expr1.Parameters);
    }

    /// <summary> AND </summary>
    public static Expression<Func<T, bool>> And<T>([NotNull] Expression<Func<T, bool>> expr1,
        [NotNull] Expression<Func<T, bool>> expr2)
    {
        var expr2Body = new RebindParameterVisitor(expr2.Parameters[0], expr1.Parameters[0]).Visit(expr2.Body);
        return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2Body), expr1.Parameters);
    }

    /// <summary> AND </summary>
    public static LambdaExpression And([NotNull] LambdaExpression expr1,
        [NotNull] LambdaExpression expr2)
    {
        var expr2Body = new RebindParameterVisitor(expr2.Parameters[0], expr1.Parameters[0]).Visit(expr2.Body);
        return Expression.Lambda(Expression.AndAlso(expr1.Body, expr2Body), expr1.Parameters);
    }
}
